#!/usr/bin/env python3
"""
数字人视频背景替换系统可执行文件打包脚本
使用PyInstaller打包成独立的可执行文件
"""
import datetime
import os
import sys
import subprocess
import shutil
import platform
from pathlib import Path
import json

class ExecutablePackager:
    def __init__(self):
        self.base_dir = Path(__file__).parent
        self.backend_dir = self.base_dir / 'backend'
        self.package_dir = self.base_dir / 'package'
        self.dist_dir = self.package_dir / 'dist'
        self.build_dir = self.package_dir / 'build'

        # 平台特定设置
        self.is_windows = platform.system() == 'Windows'
        self.exe_name = 'DigitalHuman' + ('.exe' if self.is_windows else '')

    def check_pyinstaller(self):
        """检查PyInstaller是否安装"""
        print("🔍 检查PyInstaller...")

        try:
            result = subprocess.run(['pyinstaller', '--version'],
                                    capture_output=True, text=True, check=True)
            print(f"✅ PyInstaller {result.stdout.strip()} 已安装")
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            print("❌ PyInstaller 未安装")
            print("安装命令: pip install pyinstaller")
            return False

    def check_build_requirements(self):
        """检查构建要求"""
        print("🔍 检查构建要求...")

        # 检查是否已构建
        app_file = self.backend_dir / 'app.py'

        if not app_file.exists():
            print("❌ 后端应用文件不存在")
            print("请先运行: python build.py")
            return False

        print("✅ 构建要求满足")
        return True

    def create_spec_file(self):
        """创建PyInstaller spec文件"""
        print("📝 创建PyInstaller配置...")

        spec_content = f'''# -*- mode: python ; coding: utf-8 -*-

import os
import sys
from pathlib import Path

# 获取项目路径
project_dir = Path(SPECPATH).parent / 'backend'
dist_dir = project_dir / 'dist'

block_cipher = None

# 收集数据文件
datas = []

# 添加前端构建文件
if dist_dir.exists():
    datas.append((str(dist_dir), 'dist'))

# 添加其他必要的数据文件
data_dirs = [
    'library',
    'templates',  # 如果有Jinja2模板
    'static',     # 如果有静态资源
]

for data_dir in data_dirs:
    data_path = project_dir / data_dir
    if data_path.exists():
        datas.append((str(data_path), data_dir))

# 收集隐藏导入
hiddenimports = [
    'flask',
    'flask_cors',
    'cv2',
    'numpy',
    'PIL',
    'sqlite3',
    'urllib3',
    'requests',
    'logging.handlers',
    'pathlib',
    'werkzeug.security',
    'jinja2',
]

a = Analysis(
    [str(project_dir / 'app.py')],
    pathex=[str(project_dir)],
    binaries=[],
    datas=datas,
    hiddenimports=hiddenimports,
    hookspath=[],
    hooksconfig={{}},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='{self.exe_name}',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=None,  # 可以添加图标文件路径
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='{self.exe_name[:-4] if self.exe_name.endswith(".exe") else self.exe_name}',
)
'''

        spec_file = self.package_dir / f'{self.exe_name[:-4] if self.exe_name.endswith(".exe") else self.exe_name}.spec'

        # 创建package目录
        self.package_dir.mkdir(exist_ok=True)

        with open(spec_file, 'w', encoding='utf-8') as f:
            f.write(spec_content)

        print(f"✅ Spec文件创建完成: {spec_file}")
        return spec_file

    def run_pyinstaller(self, spec_file):
        """运行PyInstaller"""
        print("📦 开始打包可执行文件...")

        try:
            # 清理旧的构建文件
            if self.build_dir.exists():
                shutil.rmtree(self.build_dir)
            if self.dist_dir.exists():
                shutil.rmtree(self.dist_dir)

            # 运行PyInstaller
            cmd = [
                'pyinstaller',
                '--clean',
                '--noconfirm',
                str(spec_file)
            ]

            print(f"执行命令: {' '.join(cmd)}")
            result = subprocess.run(cmd, cwd=self.package_dir, check=True)

            print("✅ PyInstaller打包完成")
            return True

        except subprocess.CalledProcessError as e:
            print(f"❌ PyInstaller打包失败: {e}")
            return False

    def create_directory_structure(self):
        """在可执行文件目录创建正确的目录结构"""
        print("📁 创建目录结构...")

        app_name = self.exe_name[:-4] if self.exe_name.endswith('.exe') else self.exe_name
        target_dir = self.dist_dir / app_name

        # 创建运行时需要的目录
        runtime_dirs = [
            'uploads',
            'outputs',
            'static',
            'library',
            'library/human',
            'library/background',
            'previews',
            'temp',
            'logs'
        ]

        for dir_name in runtime_dirs:
            dir_path = target_dir / dir_name
            dir_path.mkdir(parents=True, exist_ok=True)

            # 创建.gitkeep文件保持目录结构
            gitkeep = dir_path / '.gitkeep'
            gitkeep.touch()

        print("✅ 目录结构创建完成")
        return True

    def create_launcher_script(self):
        """创建启动脚本"""
        print("📝 创建启动脚本...")

        app_name = self.exe_name[:-4] if self.exe_name.endswith('.exe') else self.exe_name
        exe_path = self.dist_dir / app_name / self.exe_name

        if not exe_path.exists():
            print(f"❌ 可执行文件不存在: {exe_path}")
            return False

        # Windows批处理文件
        if self.is_windows:
            launcher_bat = self.dist_dir / f'启动{app_name}.bat'
            with open(launcher_bat, 'w', encoding='utf-8') as f:
                f.write(f'''@echo off
chcp 65001 > nul
title 数字人视频背景替换系统
echo.
echo ==========================================
echo   数字人视频背景替换系统
echo ==========================================
echo.
echo 🚀 正在启动系统...
echo 🌐 启动后访问: http://localhost:5001
echo 🛑 停止服务: 关闭此窗口或按 Ctrl+C
echo.

cd /d "%~dp0\\{app_name}"
"{self.exe_name}"

echo.
echo 系统已停止运行
pause
''')

        # Linux/macOS shell脚本
        launcher_sh = self.dist_dir / f'start_{app_name}.sh'
        with open(launcher_sh, 'w', encoding='utf-8') as f:
            f.write(f'''#!/bin/bash

echo "=========================================="
echo "   数字人视频背景替换系统"
echo "=========================================="
echo
echo "🚀 正在启动系统..."
echo "🌐 启动后访问: http://localhost:5001"
echo "🛑 停止服务: 按 Ctrl+C"
echo

cd "$(dirname "$0")/{app_name}"
./{self.exe_name}

echo
echo "系统已停止运行"
read -p "按回车键退出..."
''')

        # 设置执行权限
        if not self.is_windows and launcher_sh.exists():
            os.chmod(launcher_sh, 0o755)

        print("✅ 启动脚本创建完成")
        return True

    def create_readme(self):
        """创建使用说明"""
        print("📖 创建使用说明...")

        app_name = self.exe_name[:-4] if self.exe_name.endswith('.exe') else self.exe_name

        readme_content = f"""# 数字人视频背景替换系统 - 可执行版本

## 快速启动

### Windows 用户
双击运行 `启动{app_name}.bat`

### Linux/macOS 用户
```bash
chmod +x start_{app_name}.sh
./start_{app_name}.sh
```

### 直接运行
```bash
cd {app_name}
./{self.exe_name}
```

## 访问地址

启动后在浏览器中访问: **http://localhost:5001**

## 重要说明

⚠️ **文件夹说明**: 系统启动后会在可执行文件目录下自动创建以下文件夹：
- `uploads/` - 上传的文件
- `outputs/` - 处理结果
- `library/` - 素材库
- `previews/` - 预览文件
- `temp/` - 临时文件
- `logs/` - 日志文件

⚠️ **首次运行**: 首次启动可能需要较长时间，请耐心等待系统初始化

## 系统要求

- **Windows**: Windows 7 及以上版本
- **Linux**: 主流Linux发行版 (Ubuntu 18.04+, CentOS 7+)
- **macOS**: macOS 10.13 及以上版本
- **内存**: 至少 2GB RAM
- **存储**: 至少 5GB 可用空间
- **网络**: 无需联网（可离线使用）

## 功能特性

✨ **数字人库管理** - 上传、预览、删除数字人视频  
✨ **素材库管理** - 管理背景图片和视频  
✨ **批量处理** - 多个数字人与背景的组合处理  
✨ **参数预设** - 保存常用的参数配置  
✨ **历史记录** - 查看处理历史和结果预览  
✨ **训练功能** - 语音模型训练和管理  

## 支持格式

### 数字人视频
- **格式**: MOV、MP4
- **要求**: 需要带透明通道（绿幕或真实透明）
- **分辨率**: 建议1080p及以下
- **时长**: 建议30秒以内

### 背景素材
- **图片**: JPG、PNG、GIF
- **视频**: MP4、MOV、AVI
- **分辨率**: 支持4K，建议1080p

## 注意事项

1. **首次启动**可能需要较长时间，请耐心等待
2. **防火墙提示**请选择允许访问
3. **杀毒软件**可能误报，请添加信任
4. **端口占用**如果5000端口被占用，请关闭占用程序
5. **存储空间**确保有足够的磁盘空间存储视频文件
6. **目录权限**确保程序目录有读写权限

## 故障排除

### 启动失败
- 检查是否有杀毒软件拦截
- 确保5000端口未被占用
- 以管理员权限运行（Windows）
- 检查目录读写权限

### 找不到文件夹
- 程序会自动在可执行文件目录创建必要文件夹
- 如果仍有问题，请手动创建上述文件夹

### 处理失败
- 检查视频格式是否正确
- 确保文件未被其他程序占用
- 检查磁盘空间是否充足

### 浏览器无法访问
- 确保程序正常启动（看到启动成功提示）
- 检查防火墙设置
- 尝试使用 http://127.0.0.1:5001

## 联系支持

如遇问题请提供：
1. 操作系统版本
2. 错误截图
3. 控制台输出日志
4. logs目录下的日志文件

## 版本信息

- **版本**: v1.0.0
- **构建时间**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- **适用平台**: {platform.system()} {platform.machine()}
"""

        readme_file = self.dist_dir / 'README.md'
        with open(readme_file, 'w', encoding='utf-8') as f:
            f.write(readme_content)

        print("✅ 使用说明创建完成")
        return True

    def copy_additional_files(self):
        """复制额外的必要文件（如果需要）"""
        print("📁 复制额外文件...")

        # 这里可以复制一些示例文件或配置文件
        # 但运行时目录会由程序自动创建

        print("✅ 额外文件处理完成")
        return True

    def show_package_summary(self):
        """显示打包摘要"""
        app_name = self.exe_name[:-4] if self.exe_name.endswith('.exe') else self.exe_name

        print("\n" + "=" * 60)
        print("🎉 可执行文件打包完成!")
        print("=" * 60)
        print("📦 打包产物:")
        print(f"  📁 程序目录: {self.dist_dir}")
        print(f"  🚀 主程序: {self.dist_dir / app_name / self.exe_name}")

        if self.is_windows:
            print(f"  🎯 启动器: {self.dist_dir / f'启动{app_name}.bat'}")
        else:
            print(f"  🎯 启动器: {self.dist_dir / f'start_{app_name}.sh'}")

        print(f"  📖 说明文档: {self.dist_dir / 'README.md'}")
        print("\n📊 程序信息:")

        # 计算程序大小
        try:
            import glob
            total_size = 0
            for file_path in Path(self.dist_dir).rglob('*'):
                if file_path.is_file():
                    total_size += file_path.stat().st_size

            size_mb = total_size / (1024 * 1024)
            print(f"  💾 程序大小: {size_mb:.1f} MB")
        except:
            print("  💾 程序大小: 计算失败")

        print(f"  🖥️  目标平台: {platform.system()} {platform.machine()}")
        print(f"  🐍 Python版本: {sys.version.split()[0]}")

        print("\n🚀 使用方法:")
        if self.is_windows:
            print(f"  双击运行: 启动{app_name}.bat")
        else:
            print(f"  运行命令: ./start_{app_name}.sh")

        print("  访问地址: http://localhost:5001")

        print("\n📝 重要提醒:")
        print("  • 程序会在可执行文件目录自动创建工作文件夹")
        print("  • 首次启动可能需要较长时间")
        print("  • 确保目录有读写权限")

        print("\n📦 分发建议:")
        print("  1. 将整个 dist 文件夹打包为 ZIP")
        print("  2. 或制作安装包（NSIS, Inno Setup等）")
        print("  3. 包含 README.md 说明文档")
        print("=" * 60)

    def package(self):
        """执行完整打包流程"""
        print("📦 开始打包数字人视频背景替换系统...")
        print("=" * 60)

        # 检查PyInstaller
        if not self.check_pyinstaller():
            return False

        # 检查构建要求
        if not self.check_build_requirements():
            return False

        # 创建spec文件
        spec_file = self.create_spec_file()
        if not spec_file:
            return False

        # 运行PyInstaller
        if not self.run_pyinstaller(spec_file):
            return False

        # 创建正确的目录结构
        if not self.create_directory_structure():
            return False

        # 创建启动脚本
        if not self.create_launcher_script():
            return False

        # 复制额外文件
        if not self.copy_additional_files():
            return False

        # 创建说明文档
        if not self.create_readme():
            return False

        # 显示打包摘要
        self.show_package_summary()

        return True

def main():
    """主函数"""
    packager = ExecutablePackager()

    try:
        success = packager.package()
        if not success:
            print("❌ 打包失败")
            sys.exit(1)
    except KeyboardInterrupt:
        print("\n🛑 打包被中断")
        sys.exit(1)
    except Exception as e:
        print(f"❌ 打包异常: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()